<template>
  <div class="dropbox">
    <span
      v-for="ball in balls"
      v-show="!ball.finish"
      :key="ball.id"
      :style="{ transform: `translate3d(${ball.currX}px, ${ball.currY}px, 0)` }"
      class="drop-ball"
    ></span>
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive } from "@vue/composition-api";

interface BallOption {
  X: number;
  Y: number;
  endX: number;
  endY: number;
  time: number;
  id: number;
  initialY?: number; // 后续添加
}
// 目标位置接口
interface EndPosition {
  endX: number;
  endY: number;
}

const MAX_BALL_COUNT = 6;
let ballId = 0;

// 抽象小球类
class DropBall {
  startX: number;
  startY: number;
  currX: number;
  currY: number;
  endX: number;
  endY: number;
  time: number;
  id: number;
  a: number;
  speedX: number;
  finish: boolean;
  initialY?: number;
  constructor({ X, Y, endX, endY, time, id }: BallOption) {
    this.startX = this.currX = X;
    this.startY = this.currY = Y;
    this.endX = endX;
    this.endY = endY;
    this.time = time * 1000; // 以毫秒为单位
    this.id = id;
    this.finish = false;
    this.speedX = this.calSpeedX();
    this.a = this.calculateA();
    this.ballActive();
  }
  // 计算X轴速度方法
  protected calSpeedX() {
    return (this.endX - this.startX) / this.time;
  }
  // 计算Y轴加速度方法，暂时只考虑平抛
  protected calculateA() {
    return (2 * Math.abs(this.endY - this.startY)) / Math.pow(this.time, 2);
  }
  // 激活动画
  ballActive() {
    let startTime = -1; // 初始化开始时间
    const move = (timeStamp: number) => {
      if (startTime === -1) startTime = timeStamp; // 记录初始时间
      const currTime = timeStamp - startTime; // 计算动画进行时间，单位毫秒
      this.currX = this.startX + currTime * this.speedX;
      this.currY = this.startY + (this.a * Math.pow(currTime, 2)) / 2;
      // 判断是否还需要做动画
      if (currTime < this.time) {
        requestAnimationFrame(move);
      } else {
        // 最后一次执行，直接到终点
        this.currX = this.endX;
        this.currY = this.endY;
        // 表示已经完成动画
        this.finish = true;
      }
    };
    requestAnimationFrame(move);
  }
}
export default defineComponent({
  props: {
    endPosition: {
      // 终点位置-->绝对单位
      required: true,
      validator(value: unknown) {
        if (value && typeof value === "object") {
          if ("endX" in value && "endY" in value) {
            return true;
          }
        }
        return false;
      },
    },
    dropTime: {
      // 动画持续时间
      type: Number,
      default: 0.5,
    },
  },
  setup(props: any) {
    const balls = reactive<DropBall[]>([]);
    // 初始化球
    const initBall = (X: number, Y: number) => {
      const endPosition = props.endPosition as EndPosition; // 存在验证器检验
      const time = props.dropTime as number; // 存在默认值
      const { endX, endY } = endPosition;
      const id = ballId++;
      const newBall = new DropBall({ X, Y, endX, endY, time, id });
      balls.push(newBall);
      newBall.ballActive();
      // 安全检测，防止小球过多
      if (balls.length > MAX_BALL_COUNT) {
        balls.shift();
      }
    };
    return {
      balls,
      initBall,
    };
  },
});
</script>
<style lang="stylus" scoped>
$ball-color = #00a0e9;

.dropbox {
  text-align: left;
}

.drop-ball {
  display: inline-block;
  position: fixed;
  top: 0;
  left: 0;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  z-index: 2;
  background-color: $ball-color;
}
</style>
